﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Pg.BioMedics.SDR.Entities;
using Pg.BioMedics.SDR.Data.Constants;

namespace Pg.BioMedics.SDR.Data
{
    /// <summary>
    /// Provides entry point for all data access logic operations related with 
    /// Annotation with related entities.
    /// </summary>
    public class AnnotationManager : BaseDataManager
    {
        #region Constructors

        /// <summary>
        /// Creates instance of AnnotationManager and provides all required 
        /// initialized according to specified data mapper strategy
        /// </summary>
        /// <param name="dataMapperStrategy"></param>
        internal AnnotationManager(IDataMapperStrategy dataMapperStrategy)
            : base(dataMapperStrategy) { }

        #endregion

        #region Methods

        /// <summary>
        /// Inserts a new annotation into the system
        /// </summary>
        /// <param name="annotation">Not saved Annotation instance</param>
        /// <returns>Saved Annotation instance</returns>
        public virtual Annotation InsertAnnotation(Annotation annotation)
        {
            try
            {
                DataMapper.Insert(AnnotationStoredProcedures.InsertAnnotation, annotation);
                ThrowInsertAnnotationException(annotation);

                return annotation;
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Could not insert a new annotation", ex);
            }
        }

        /// <summary>
        /// Sets annotation status
        /// </summary>
        /// <param name="annotation">Saved Annotation instance</param>
        /// <param name="statusCode">Annotation status code</param>
        /// <param name="author">Annotation status author</param>
        public virtual void SetAnnotationStatus(Annotation annotation, string statusCode, Person author)
        {
            try
            {
                Dictionary<string, object> parameters = new Dictionary<string, object>();
                parameters.Add("AnnotationId", annotation.Id);
                parameters.Add("StatusCode", statusCode);
                parameters.Add("PersonId", author != null ? author.Id : null);

                DataMapper.Insert(AnnotationStoredProcedures.SetAnnotationStatus, parameters);
                ThrowSetAnnotationStatusException((int)parameters["Result"], annotation, statusCode, author);
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Could not set status for annotation ", ex);
            }
        }

        /// <summary>
        /// Gets annotation by its unique id
        /// </summary>
        /// <param name="annotationId">Annotation unique id</param>
        /// <returns>Annotation or null if annotation  couldnot be located</returns>
        public virtual Annotation GetAnnotationById(int annotationId)
        {
            try
            {
                Dictionary<string, object> parameters = new Dictionary<string,object>();
                parameters.Add("AnnotationId", annotationId);

                return DataMapper.QueryForObject<Annotation>(AnnotationStoredProcedures.GetAnnotationById, parameters);
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Couldnot get annotation by id {0}", ex, annotationId);
            }
        }

        /// <summary>
        /// Gets a list of all annotations stored in the database
        /// </summary>
        /// <returns>List with annotations</returns>
        public virtual IList<Annotation> GetAllAnnotations()
        {
            try
            {
                return DataMapper.QueryForList<Annotation>(AnnotationStoredProcedures.GetAllAnnotations, 
                                                           new Dictionary<string, object>());
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Could not get all annotations", ex);
            }
        }

        /// <summary>
        /// Gets annotation by ASR task unique id
        /// </summary>
        /// <param name="asrTaskUniqueId">ASR task unique id</param>
        /// <returns>Annotation related to given task or null if not found</returns>
        public virtual Annotation GetAnnotationByAsrTaskUniqueId(Guid asrTaskUniqueId)
        {
            try
            {
                Dictionary<string, object> parameters = new Dictionary<string, object>();
                parameters.Add("AsrTaskUniqueId", asrTaskUniqueId);

                return DataMapper.QueryForObject<Annotation>(AnnotationStoredProcedures.GetAnnotationByAsrTaskUniqueId, parameters);
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Couldnot get annotation by asr task unique id {0}", ex, asrTaskUniqueId);
            }
        }

        /// <summary>
        /// Gets list of annotations assigned to the given document
        /// </summary>
        /// <param name="document">Existing document</param>
        /// <returns>List of annotations</returns>
        public virtual IList<Annotation> GetAnnotationsByDocument(Document document)
        {
            try
            {
                Dictionary<string, object> parameters = new Dictionary<string, object>();
                parameters.Add("DocumentId", document.Id);

                return DataMapper.QueryForList<Annotation>(AnnotationStoredProcedures.GetAnnotationByDocumentId, parameters);
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Couldnot get annotations by document id", ex);
            }
        }

        /// <summary>
        /// Updates document annotation
        /// </summary>
        /// <param name="annotation">Annotation which should be updated</param>
        public virtual void UpdateAnnotation(Annotation annotation)
        {
            try
            {
                DataMapper.Update(AnnotationStoredProcedures.UpdateAnnotation, annotation);
                ThrowUpdateAnnotationException(annotation);
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Could not update annotation", ex);
            }
        }         

        /// <summary>
        /// Inserts a new token into the database
        /// </summary>
        /// <param name="token">Token entity</param>
        /// <returns>Saved token</returns>
        public virtual Token InsertToken(Token token)
        {
            try
            {
                DataMapper.Insert(AnnotationStoredProcedures.InsertToken, token);
                ThrowInsertTokenException(token);

                return token;
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Couldnot insert token", ex);
            }
        }

        /// <summary>
        /// Gets token by its unique id
        /// </summary>
        /// <param name="tokenId">Token unique id</param>
        /// <returns>Token instance</returns>
        public virtual Token GetTokenById(int tokenId)
        {
            try
            {
                Dictionary<string, object> parameters = new Dictionary<string,object>();
                parameters.Add("TokenId", tokenId);

                return DataMapper.QueryForObject<Token>(AnnotationStoredProcedures.GetTokenById, parameters);
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Couldnot get token by id {0}", ex, tokenId);
            }
        }

        /// <summary>
        /// Gets tokens holded by the given annotation 
        /// </summary>
        /// <param name="annotation">Document annotation</param>
        /// <returns>List of tokens from given annotation</returns>
        public virtual IList<Token> GetTokensByAnnotation(Annotation annotation)
        {
            try
            {
                Dictionary<string, object> parameters = new Dictionary<string, object>();
                parameters.Add("AnnotationId", annotation.Id);

                return DataMapper.QueryForList<Token>(AnnotationStoredProcedures.GetTokensByAnnotationId, parameters);
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Couldnot get tokens by annotation", ex);
            }
        }

        /// <summary>
        /// Gets all tokens stored in the data store
        /// </summary>
        /// <returns>List with tokens</returns>
        public virtual IList<Token> GetAllTokens()
        {
            try
            {
                return DataMapper.QueryForList<Token>(AnnotationStoredProcedures.GetAllTokens, 
                                                      new Dictionary<string, object>());
            }
            catch (Exception ex)
            {
                throw new SDRDataException("Couldnot get all tokens", ex);
            }
        }

        #endregion

        #region Support methods

        /// <summary>
        /// Throws appropriate exception related with error code returned 
        /// by InsertAnnotation stored procedure
        /// </summary>
        /// <param name="annotation">Annotation entity</param>
        private void ThrowInsertAnnotationException(Annotation annotation)
        {
            switch (annotation.Id)
            {
                case -1:
                    throw new SDRDataException("Document {0} doesnot exist", annotation.Id);
                case -2:
                    throw new SDRDataException("Annotation type code {0} is invalid", annotation.TypeCode);
                case -3:
                    throw new SDRDataException("Content {0} doesnot exist", annotation.ContentId);
                case -4:
                    throw new SDRDataException("Language code {0} is invalid", annotation.LanguageCode);
            }
        }

        /// <summary>
        /// Throws approperiate exception related with an error code returned 
        /// by the SetAnnotationstatusstored procedure
        /// </summary>
        /// <param name="result">Procedure result code</param>
        /// <param name="annotation">Annotation instance</param>
        /// <param name="statusCode">Annotation status code</param>
        /// <param name="author">Status author</param>
        private void ThrowSetAnnotationStatusException(int result, Annotation annotation, string statusCode, Person author)
        {
            switch (result)
            {
                case -1:
                    throw new SDRDataException("Annotation {0} doesnot exist", annotation.Id);
                case -2:
                    throw new SDRDataException("Annotation status code {0} is invalid", statusCode);
                case -3:
                    throw new SDRDataException("Annotation author {0} does not exist", author.Id);
            }
        }

        /// <summary>
        /// Throws appropriate exception related with an error code returned by 
        /// UpdateAnnotation stored procedure.
        /// </summary>
        /// <param name="annotation"></param>
        private void ThrowUpdateAnnotationException(Annotation annotation)
        {
            switch (annotation.Id)
            {
                case -1:
                    throw new SDRDataException("Annotation doesnot exist");
                case -2:
                    throw new SDRDataException("Document {0} doesnot exist", annotation.Id);
                case -3:
                    throw new SDRDataException("Annotation type code {0} is invalid", annotation.TypeCode);
                case -4:
                    throw new SDRDataException("Content {0} doesnot exist", annotation.ContentId);
                case -5:
                    throw new SDRDataException("Language code {0} is invalid", annotation.LanguageCode);
            }
        }

        /// <summary>
        /// Throws appropriate exception for error code returned by InsertToken stored procedure
        /// </summary>
        /// <param name="token">Token instance</param>
        private void ThrowInsertTokenException(Token token)
        {
            switch (token.Id)
            {
                case -1:
                    throw new SDRDataException("Token type code {0} is invalid", token.TypeCode);
                case -2:
                    throw new SDRDataException("Annotation {0} doesnot exist", token.AnnotationId);
            }
        }

        #endregion
    }
}
